package calc

import scala.collection.mutable.Map

case class Function[T](val f: T => T) extends Token[T]

abstract class FunctionCalculator extends Calculator{
  val functions: Map[String, (T => T)] = Map()
  
  def addFunction(s: String, f: (T => T)){
    functions += (s -> f)
  }
  
  override def asComputable(token: Token[T]): Computable[T] =
    token match {
      case Function(f) => Computable(f)
      case _ => super.asComputable(token)
    }
  
  override def tokenize(chars: List[Char], uminusPossible: Boolean = true): List[Token[T]] = {
    object FunctionCallExtract {
      def functionName(s: String): Option[String] = """\w[\w\d_]*\(""".r.findPrefixOf(s)
      def unapply(c: List[Char]): Option[(String, List[Char])] = functionName(c.mkString).map((n: String) => (n.dropRight(1), c.drop(n.length)))
    }
    
    chars match {
      case FunctionCallExtract(functionName, tail) => Function[T](functions(functionName)) :: tokenize(tail, false)
      case _ => super.tokenize(chars, uminusPossible)
    }
  }
  
  override def infix2postfix(tokens: List[Token[T]], stack: List[Token[T]] = List[Token[T]]()): List[Token[T]] =
    tokens match {
      case (f @ Function(_)) :: tail => infix2postfix(tail, f :: OpenParen[T]() :: stack)
      case _ => super.infix2postfix(tokens, stack)
  }
}

trait TrigonometricFunctions{
  this: FunctionCalculator =>
  override type T = Double
  
  addFunction("sin", (x: T) => Math.sin(x))
  addFunction("cos", (x: T) => Math.sin(x))
  addFunction("tan", (x: T) => Math.sin(x))
}  

trait OtherFunctions{
  this: FunctionCalculator =>
  override type T = Double

  addFunction("exp", (x: T) => Math.exp(x))
}  

trait NotVeryUsefulFunctions{
  this: FunctionCalculator =>
  override type T = Hex

  addFunction("dostuff", (x: T) => new Hex(x.value + 1))
}  

